package de.gbeine.java.depview.ui.graph;

import java.awt.BorderLayout;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;

import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;

import org.apache.commons.collections15.Transformer;
import org.apache.commons.collections15.functors.ConstantTransformer;

import com.google.common.eventbus.EventBus;

import de.gbeine.java.depview.event.AddFilterEvent;
import de.gbeine.java.depview.event.SelectElementEvent;
import de.gbeine.java.depview.layout.ChangeLayoutStrategy;
import de.gbeine.java.depview.model.Element;
import de.gbeine.java.depview.ui.controller.GraphPanel;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.SpringLayout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.GraphMouseListener;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;

/**
 *
 */
public final class VisualizationViewer extends JPanel implements GraphPanel {

    /**
     *
     */
    private static final long serialVersionUID = -4374455100571289060L;

    /**
     *
     */
    private final EventBus eventBus;

    /**
     *
     */
    private final JPopupMenu popupMenu;

    /**
     *
     */
    private final Transformer<Element, String> vertexToolTipTransformer = new ToStringLabeller<Element>();

    /**
     *
     */
    private final Transformer<Element, String> vertexStringer = new ElementLabeller();

    /**
     *
     */
    private final ModalGraphMouse graphMouse = new DefaultModalGraphMouse<Integer, Number>();

    /**
     *
     */
    private final Transformer<Context<Graph<Element, Number>, Number>, Shape> edgeShapeTransformer =
        new EdgeShape.Line<Element, Number>();

    /**
     *
     */
    private final GraphMouseListener<Element> graphMouseListener = new GraphMouseListener<Element>() {

        @Override
        public void graphClicked(final Element v, final MouseEvent me) {
            VisualizationViewer.this.graphClicked(v, me);
        }

        @Override
        public void graphPressed(final Element v, final MouseEvent me) {
        }

        @Override
        public void graphReleased(final Element v, final MouseEvent me) {
        }
    };

    /**
     *
     */
    private edu.uci.ics.jung.visualization.VisualizationViewer<Element, Number> visualizationViewer;

    /**
     *
     */
    private Element selected;

    /**
     *
     * @param pLayout
     */
    public VisualizationViewer(final EventBus pEventBus) {
        super();
        this.eventBus = pEventBus;
        this.popupMenu = new JPopupMenu();
        this.initialize();
    }

    private void initialize() {

        final JMenuItem mntmPickElement = new JMenuItem("Pick element");
        mntmPickElement.addActionListener(new ActionListener() {
            public void actionPerformed(final ActionEvent e) {
                VisualizationViewer.this.togglePicked();
            }
        });
        this.popupMenu.add(mntmPickElement);

        final JSeparator sprPick = new JSeparator();
        this.popupMenu.add(sprPick);

        final JMenuItem mntmShowOutboundDependencies = new JMenuItem("Show outbound dependencies");
        mntmShowOutboundDependencies.addActionListener(new ActionListener() {
            public void actionPerformed(final ActionEvent e) {
                VisualizationViewer.this.showOutboundDependencies();
            }
        });
        this.popupMenu.add(mntmShowOutboundDependencies);

        final JMenuItem mntmHideOutboundDependencies = new JMenuItem("Hide outbound dependencies");
        mntmHideOutboundDependencies.addActionListener(new ActionListener() {
            public void actionPerformed(final ActionEvent e) {
                VisualizationViewer.this.hideOutboundDependencies();
            }
        });
        this.popupMenu.add(mntmHideOutboundDependencies);

        final JSeparator sprOutbound = new JSeparator();
        this.popupMenu.add(sprOutbound);

        final JMenuItem mntmShowInboundDependencies = new JMenuItem("Show inbound dependencies");
        mntmShowInboundDependencies.addActionListener(new ActionListener() {
            public void actionPerformed(final ActionEvent e) {
                VisualizationViewer.this.showInboundDependencies();
            }
        });
        this.popupMenu.add(mntmShowInboundDependencies);

        final JMenuItem mntmHideInboundDependencies = new JMenuItem("Hide inbound dependencies");
        mntmHideInboundDependencies.addActionListener(new ActionListener() {
            public void actionPerformed(final ActionEvent e) {
                VisualizationViewer.this.hideInboundDependencies();
            }
        });
        this.popupMenu.add(mntmHideInboundDependencies);

        final JSeparator sprInbound = new JSeparator();
        this.popupMenu.add(sprInbound);

        final JMenuItem mntmAddToFilter = new JMenuItem("Add to filter");
        mntmAddToFilter.addActionListener(new ActionListener() {
            public void actionPerformed(final ActionEvent e) {
                VisualizationViewer.this.addFilter();
            }
        });
        this.popupMenu.add(mntmAddToFilter);

        this.setLayout(new BorderLayout(0, 0));
    }

    /**
     *
     * @param pLayout
     */
    private void init(final Layout<Element, Number> pLayout) {
        this.visualizationViewer = new edu.uci.ics.jung.visualization.VisualizationViewer<Element, Number>(pLayout);
        this.visualizationViewer.setVertexToolTipTransformer(this.vertexToolTipTransformer);
        this.visualizationViewer.setGraphMouse(this.graphMouse);
        this.visualizationViewer.getRenderContext().setEdgeShapeTransformer(this.edgeShapeTransformer);
        this.visualizationViewer.getRenderContext().setVertexFillPaintTransformer(
            new PickableVertexPaintTransformer<Element>(
                this.visualizationViewer.getPickedVertexState(),
                java.awt.Color.RED, java.awt.Color.YELLOW)
            );
        this.visualizationViewer.getRenderContext().setVertexLabelTransformer(this.vertexStringer);
        this.visualizationViewer.addGraphMouseListener(this.graphMouseListener);
        this.add(this.visualizationViewer, java.awt.BorderLayout.CENTER);
    }

    @Override
    public void setGraph(final Graph<Element, Number> pGraph) {
        if (null == this.visualizationViewer) {
            this.init(new SpringLayout<Element, Number>(pGraph));
        } else {
            this.visualizationViewer.getGraphLayout().setGraph(pGraph);
            this.visualizationViewer.repaint();
        }
    }

    @Override
    public void setGraphLayout(final ChangeLayoutStrategy pLayout) {
        this.visualizationViewer.setGraphLayout(
            pLayout.newLayout(
                this.visualizationViewer.getGraphLayout().getGraph()
            )
        );
    }

    @Override
    public void setMouseMode(final Mode pMode) {
        this.graphMouse.setMode(pMode);
    }

    @Override
    public void showLabels(final boolean pShow) {
        if (pShow) {
            this.visualizationViewer.getRenderContext().setVertexLabelTransformer(this.vertexStringer);
        } else {
            this.visualizationViewer.getRenderContext().setVertexLabelTransformer(new ConstantTransformer(null));
        }
        this.visualizationViewer.repaint();
    }

    /**
     *
     * @param v
     * @param me
     */
    private void graphClicked(final Element v, final MouseEvent me) {
        if (MouseEvent.BUTTON1 == me.getButton()) {
            this.selected = v;
            this.togglePicked();
            this.eventBus.post(new SelectElementEvent(this, v));
        }
        if (MouseEvent.BUTTON3 == me.getButton()) {
            this.selected = v;
            this.popupMenu.show(this.visualizationViewer, me.getX(), me.getY());
        }
    }

    /**
     *
     */
    private void addFilter() {
        final AddFilterEvent event = new AddFilterEvent(this, this.selected.getName());
        this.eventBus.post(event);
    }

    /**
     *
     */
    private void togglePicked() {
        this.visualizationViewer.getPickedVertexState().pick(
                this.selected,
                !this.visualizationViewer.getPickedVertexState().isPicked(this.selected)
            );
    }

    /**
     *
     */
    private void showOutboundDependencies() {
        // If the selected item is not yet picked, pick it!
        if (!this.visualizationViewer.getPickedVertexState().isPicked(this.selected)) {
            this.visualizationViewer.getPickedVertexState().pick(this.selected, true);
        }
        // Pick all the neighbors of the selected item
        for (final Element neighbor : this.visualizationViewer.getGraphLayout()
                .getGraph().getSuccessors(this.selected)) {
            this.visualizationViewer.getPickedVertexState().pick(neighbor, true);
        }
    }

    /**
     *
     */
    private void hideOutboundDependencies() {
        // If the selected item is not yet picked, pick it!
        if (!this.visualizationViewer.getPickedVertexState().isPicked(this.selected)) {
            this.visualizationViewer.getPickedVertexState().pick(this.selected, true);
        }
        // Unpick all the neighbors of the selected item
        for (final Element neighbor : this.visualizationViewer.getGraphLayout()
                .getGraph().getSuccessors(this.selected)) {
            this.visualizationViewer.getPickedVertexState().pick(neighbor,false);
        }
    }

    /**
     *
     */
    private void showInboundDependencies() {
        // If the selected item is not yet picked, pick it!
        if (!this.visualizationViewer.getPickedVertexState().isPicked(this.selected)) {
            this.visualizationViewer.getPickedVertexState().pick(this.selected, true);
        }
        // Pick all the neighbors of the selected item
        for (final Element neighbor : this.visualizationViewer.getGraphLayout()
                .getGraph().getPredecessors(this.selected)) {
            this.visualizationViewer.getPickedVertexState().pick(neighbor, true);
        }
    }

    /**
     *
     */
    private void hideInboundDependencies() {
        // If the selected item is not yet picked, pick it!
        if (!this.visualizationViewer.getPickedVertexState().isPicked(this.selected)) {
            this.visualizationViewer.getPickedVertexState().pick(this.selected, true);
        }
        // Unpick all the neighbors of the selected item
        for (final Element neighbor : this.visualizationViewer.getGraphLayout()
                .getGraph().getPredecessors(this.selected)) {
            this.visualizationViewer.getPickedVertexState().pick(neighbor, false);
        }
    }
}
